import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import org.xml.sax.SAXException;

/**
 * This class contains all functionality with respect to handling an XML file  within the eb-forms application.
 * It contains functionality for reading, editing and writing the xml structure and xml values.
 * @author AXXXE
 *
 */
public class XMLHandler {

	private AnnotationfileHandler annotationFileHandler;
	private SchemafileHandler schemaFileHandler;
	private String annotationFilename;
	private String schemaFilename;
	private String configFilename;
	private String configFilePath;
	private Document configDoc;

	/* Constructor(s) */

	/**
	 * Generates a new XMLHandler creating the path to the schema and annotation file. 
	 * @param filename the filename of the configuration file.
	 **/
	public XMLHandler(String filename) throws EbFormsException {
		try {
			configFilePath = filename;
			String sep = File.separator;
			int index = configFilePath.lastIndexOf(sep);

			configFilename = configFilePath.substring(index + 1);
			configFilePath = configFilePath.substring(0, index + 1);

			configDoc = readFile(filename);
			readConfigDocument(configDoc);

			annotationFileHandler = new AnnotationfileHandler(
					annotationFilename);
			schemaFileHandler = new SchemafileHandler(schemaFilename);
		} catch (Exception e) {
			throw new EbFormsException("Invalid configuration file");
		}

	}

	/* Private methods */

	/**
	 * reads the location and constructs a Document datastructure of the annotation file and the xml schema.   
	 * @param configDoc the xml document which contains the location of the annotation and schema file.
	 **/
	private void readConfigDocument(Document configDoc) throws IOException,
			SAXException, ParserConfigurationException {
		Node n = configDoc.getDocumentElement();
		if (n.hasChildNodes()) {
			n = n.getFirstChild();
		} else {
			n = null;
		}
		boolean succes = false;
		while (n != null) {
			if (n.getNodeName() == "schema") {
				schemaFilename = n.getAttributes().getNamedItem("src")
						.getNodeValue();
				schemaFilename = configFilePath + schemaFilename;
				if (succes)
					return;
				succes = true;
			} else if (n.getNodeName() == "annotation") {
				annotationFilename = n.getAttributes().getNamedItem("src")
						.getNodeValue();
				annotationFilename = configFilePath + annotationFilename;
				if (succes)
					return;
				succes = true;
			}
			n = n.getNextSibling();
		}
	}

	/* Static methods for use by SchemafileHandler, AnnotationfileHandler and this class itself */

	/**
	 * method for saving <tt>fn</tt> as value for the annotation location within the configuration file.  
	 * @param fn the filename of the saved annotation file
	 * @throws IOException
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 */
	public void saveConfigFile(String fn) throws EbFormsException {
		try {
			Node n = configDoc.getDocumentElement();
			if (n.hasChildNodes()) {
				n = n.getFirstChild();
			} else {
				n = null;
			}
			while (n != null) {
				if (n.getNodeName() == "annotation") {
					n.getAttributes().getNamedItem("src").setNodeValue(fn);
				}
				n = n.getNextSibling();
			}
	
			writeFile(configFilePath + configFilename);
		} catch (Exception e) {
			throw new EbFormsException("Unable to save configuration file");
		}
	}

	/**
	 *  method for writing the location <tt>fn</tt> of the annotationfile of the configDoc to 
	 *  the real xml config document 
	 * @param fn
	 */
	private void writeFile(String fn) throws TransformerConfigurationException,
			IOException, TransformerException {
		Transformer transformer = TransformerFactory.newInstance()
				.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
	
		//initialize StreamResult with File object to save to file
		StreamResult result = new StreamResult(new StringWriter());
		DOMSource source = new DOMSource(configDoc);
		transformer.transform(source, result);
	
		String xmlString = result.getWriter().toString();
		java.io.FileWriter writer = new java.io.FileWriter(fn);
		writer.write(xmlString);
		writer.flush();
		writer.close();
	
	}

	/**
	 * Getter of the <tt>annotationfileHandler</tt>
	 * @return the annotationfileHandler
	 */
	public AnnotationfileHandler getAnnotationfileHandler() {
		return annotationFileHandler;
	}

	/**
	 * Getter of the <tt>schemafileHandler</tt>
	 * @return the schemafileHandler
	 */
	public SchemafileHandler getSchemafileHandler() {
		return schemaFileHandler;
	}

	/**
	 * Getter of the property <tt>annotationFilename</tt>
	 * @return the annotationfileName
	 */
	public String getAnnotationfileName() {
		return annotationFilename;
	}

	/**
	 * Getter of the property <tt>configFilePath</tt>
	 * @return the configFilePath
	 */
	public String getConfigFilePath() {
		return configFilePath;
	}

	/* Static methods for use by SchemafileHandler, AnnotationfileHandler and this class itself */
	
	/**
	 * gets the childNode with name <tt>name</tt> of the current Node <tt>n</tt>.   
	 * @param n the current node
	 * @param name the name of the child node.
	 * @return the node with name <tt>name</tt>, null if not found
	 **/
	public static Node getChildNodeByName(Node n, String name) {
		if (n.hasChildNodes()) {
			n = n.getFirstChild();
			while (n != null) {
				if (n.getNodeName().equals(name)) {
					return n;
				}
				n = n.getNextSibling();
			}
		} //Has no children, or these have not been found
		return null;
	}

	/**
	 * Generates a Document data structure with filename <tt>fn</tt>
	 * @param fn the filename of the document
	 * @return a Document with filename <tt>fn</tt>
	 **/
	public static Document readFile(String fn) throws IOException,
			SAXException, ParserConfigurationException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		return builder.parse(new File(fn));
	}

	/**
	 * This method returns the abstract hierarchy for a given 
	 * dictionary entry name. 
	 * @param den The dictionary entry name for which to 
	 * obtain the abstract hierarchy. The den must have a space
	 * after every . and _ following the UN/CEFACT standard.
	 * @return A Vector containing all the Strings with the elements
	 * of the hierarchy. The first is the most specific, the following
	 * items are all more generic.
	 * This method returns an empty vector if the den cannot be processed. (For example: is empty).
	 */
	
	public static Vector<String> getAbstractHierarchy(String den) {
	
		if (den == null || den.equals(""))
			return new Vector<String>();
	
		Vector<String> vAbstractHierarchy = new Vector<String>();
		vAbstractHierarchy.add(den); //Most specific description
	
		String strClass; //String class part of the name
		String strRole; //String role part of the name
		String strType; //String type part of the name		
	
		int pointPos = den.indexOf("."); //position of the first dot.
		int pointPos2 = den.indexOf(".", pointPos + 1); //position of the second dot.
		int pos; //Working variable in the loop. position of the _
	
		if (pointPos2 > -1) { // two dots in the name
			strClass = den.substring(0, pointPos + 1);
			strRole = den.substring(pointPos + 1, pointPos2 + 1);
			strType = den.substring(pointPos2 + 1, den.length());
	
			String temp; //another working variable in the loops
			boolean loopAgain = true;
			//As long as Type is not yet the most generic, execute procedure. But: at least once.
			do { //loop for the type.
				temp = strClass;
				do { //loop for the  class			
					pos = temp.indexOf("_");
					if (pos > -1) {
						//According to the UN/CEFACT standard, a space is always included behind
						//a _ or ., so pos + 2
						temp = temp.substring(pos + 2);
						vAbstractHierarchy.add(temp + strRole + strType); //Minder specifieke beschrijving op basis van Class
					}
				} while (temp.indexOf("_") > -1); //end of loop for the class
	
				temp = strRole;
				do { //loop for the role. Looks like the one for the class
					pos = temp.indexOf("_");
					if (pos > -1) {
						temp = temp.substring(pos + 1);
						vAbstractHierarchy.add(strClass + temp + strType); //Minder specifieke beschrijving op basis van Role
					}
	
				} while (temp.indexOf("_") > -1); //end of loop for the role.
	
				vAbstractHierarchy.add(strType.substring(1) + ". Details");
	
				if (strType.indexOf("_") > -1) {
					//Type can be more generic. 
					//Break it off, and execute the whole loop again.
					pos = strType.indexOf("_");
					strType = strType.substring(pos + 1);
					loopAgain = true;
				} else {
					loopAgain = false;
				}
	
			} while (loopAgain); //end of loop for the type
		} else { // Only one . in the (type_ subthing_ whatever. Details)
			while ((pos = den.indexOf("_")) > -1) {
				den = den.substring(pos + 2);
				vAbstractHierarchy.add(den);
			}
		}
		return vAbstractHierarchy;
	}
}
